<?php

class gl_point
{
	// width, height
	private $_w;
	private $_h;
	
	public function __construct( $p )
	{
		// create from gl_point or equals
		if( $p instanceof gl_point ) {
			$this ->_w = $p ->w;
			$this ->_h = $p ->h;
			return;
		}
		
		$p = func_get_args( );
		
		if( count($p)>1 ) {
			$this->_w = intval( $p[0] );
			$this->_h = intval( $p[1] );
		} else {
			if( strpos( $p[0], 'x' ) ) { list($w,$h) = explode('x',$p[0]); $w=intval($w); $h=intval($h); } else { $w = $h = intval($p[0]); };
			$this->_w = intval( $w );
			$this->_h = intval( $h );
		}
	}
	
	public function __toString( )
	{
		return $this->_w.'x'.$this->_h;
	}
	
	public function __get( $v )
	{
		switch( $v ) {
		case 'x':case 'w':	return $this->_w;
		case 'y':case 'h':	return $this->_h;
		case 'short':		if( $this->_w ) { return $this->is_square() ? $this->_w : ($this->_w.'x'.$this->_h); }
		default:	return null;
		}
	}
	
	public function is_square( ) { return ($this ->_w && $this->_w === $this->_h); }
}

class gl_size extends gl_point { };
class gl_position extends gl_point { };

class gl_rect
{
	// left & top
	private $_x;
	private $_y;
	private $_w;
	private $_h;
	
	public function __construct( $r )
	{
		// create from gl_point or equals
		if( $r instanceof gl_rect ) {
			$this ->_x = $p ->x; $this ->_y = $p ->y;
			$this ->_w = $p ->w; $this ->_h = $p ->h;
			return;
		}
		
		$p = func_get_args( );
		if( count($p)==2 && $p[0] instanceof gl_point && $p[1] instanceof gl_point )
		{
			$this ->_x = $p[0] ->x; $this ->_y = $p[0] ->y;
			$this ->_w = $p[1] ->w; $this ->_h = $p[1] ->h;
			return;
		}
		
		if( count($p)==4 ) {
			$this->_x = intval( $p[0] ); $this->_y = intval( $p[1] );
			$this->_w = intval( $p[2] ); $this->_h = intval( $p[3] );
		} elseif( preg_match( '/^((\\d+)[,x](\\d+)|(\\d+))[ :]+((\\d+)x(\\d+)|(\\d+))$/', $r, $p ) ) {
			if( !isset( $p[4] ) || !$p[4] ) { $this->_x = (int)$p[2]; $this->_y = (int)$p[3]; } else { $this->_x = $this->_y = (int)$p[4]; }
			if( !isset( $p[8] ) || !$p[8] ) { $this->_w = (int)$p[6]; $this->_h = (int)$p[7]; } else { $this->_w = $this->_h = (int)$p[8]; }
		} else {
		}
	}
	
	public function __toString( )
	{
		return sprintf( "%dx%d%+d%+d", $this->_w, $this->_h, $this->_x, $this->_y );
	}
	
	public function is_square( ) { return ($this ->_w && $this->_w === $this->_h); }
	public function calc_square( ) { return $this->_w*$this->h; }
	
	public function get_position( ) { return new gl_point($this->_x,$this->_y); }
	public function get_size( ) { return new gl_size($this->_w,$this->_h); }
	public function get_array( ) { return array( $this ->get_position( ), $this ->get_size( ) ); }
	
	public function __get( $v )
	{
		switch( $v ) {
		case 'x':	return $this->_x;
		case 'y':	return $this->_y;
		case 'w':	return $this->_w;
		case 'h':	return $this->_h;
		case 'r':	return $this->_x+$this->_w;
		case 'b':	return $this->_y+$this->_h;
		case 'short':	if( $this->_w ) { return $this->is_square() ? $this->_w : ($this->_w.'x'.$this->_h); }
		default:	return null;
		}
	}
}



class gl extends singleton
{
	// php singleton wrapper
	static public function &instance() { return parent::instance(__CLASS__); }
	
	protected function __construct( )
	{
		$this ->quality = 90;
		// ...
	}
	
	/**
	 * Функция пересохраняет изображение с возможностью подрезать/растянуть.
	 * 
	 * При указании данный о вырезаемой области или о размере изображения
	 * - функция оставляет только указанную область и растягивает его как указано.
	 *
	 * @param	string $s	путь к исходному файлу картинки
	 * @param	string $d	путь к конечного изображения
	 * @param	gl_rect $f	объект с данными о вырезаемой области (crop) *** не пахает. todo!
	 * @param	gl_size $t	размер конечного изображения (scale)
	 * @param	string $fmt	формат конечного файла изображения (по-умолчанию - достаем из имени файла. или jpg)
	 * @return	boolean		ежели всё хорошо - тогда тру. а так - файльше.
	 */
	public function save_crop( $s, $d, gl_rect $f = null, gl_size $t = null, $format = null )
	{
		if( !is_null( $format ) )
			$d = sprintf( "%s/%s.%s", pathinfo( $d, PATHINFO_DIRNAME ), pathinfo( $d, PATHINFO_FILENAME ), $format );
		
		$c0 = sprintf( "convert -background white -flatten -quality 100%% -fuzz 1%% -trim %s +repage %s %s",
			(!is_null($f)?sprintf( '-crop %1$s ', $f ):''),
			addslashes( $s ),
			addslashes( $d ) );
		$c1 = sprintf( 'convert -quality %3$d%% %1$s %2$s %2$s',
			(!is_null($t)?sprintf( '-resize %1$s%2$s ', $t, (is_null($f)?'\>':'') ):''),
			addslashes( $d ),
			$this ->quality );
		
		// must be fixed!
//		echo $c0."\n".$c1."\n";
		$this ->log( '1. '.$c0 );
		exec( $c0, $o, $v );
		$this ->log( '1! '.$v." ".join( ', ', $o ) );
		//var_dump( $o, $v );
		$this ->log( '2. '.$c1 );
		exec( $c1, $o, $v );
		$this ->log( '2! '.$v." ".join( ', ', $o ) );
		//var_dump( $o, $v );
		
		return !!filesize( $d );
	}
	
	/**
	 * Функция тупо изменяет размер файла
	 *
	 * @param	string	$s	путь к исходному файлу картинки
	 * @param	string	$d	путь к конечного изображения
	 * @param	mixed	$i	int|gl_size
	 */
	public function resize( $s, $d, $i = null )
	{
		$i = is_null( $i ) ? 100 : (string)($i);
		$c = sprintf( 'convert %2$s -quality %4$d%% -resize %1$s %3$s',
			$i,
			escapeshellarg($s),
			escapeshellarg($d),
			$this ->quality );
		
		$this ->log( '1. '.$c );
		exec( $c, $o, $v );
		$this ->log( '1! '.$v." ".join( ', ', $o ) );
	}
	
	
	
	/**
	 * MIME checkers
	 *
	 */
	public function get_file_mime( $s )
	{
		if( !is_readable( $s ) || !is_file( $s ) ) return null;
		$fi = new finfo( FILEINFO_MIME, config::instance()->fileinfo__magicfile );
		$mime = $fi->file( $s );

		$this ->log( $s.' has mime '.$mime );
		return $mime;
	}
	
	public function is_image_mime( $mime )
	{
		return 1==preg_match( '@^image/[a-z\-]+@i', $mime );
	}
	
	public function is_video_mime( $mime )
	{
		return 1==preg_match( '@^video/[a-z\-]+@i', $mime );
	}

	public function is_flash_video_mime( $mime )
	{
		return 1==preg_match( '@^video/x-flv@i', $mime );
	}
	
	public function is_flash_mime( $mime )
	{
		return 1==preg_match( '@^application/x-shockwave-flash@i', $mime );
	}
	
	public function is_image_file( $s )
	{
		if( !($mime = $this ->get_file_mime( $s )) ) return $mime;
		return $this ->is_image_mime( $mime );
	}

	public function is_video_file( $s )
	{
		if( !($mime = $this ->get_file_mime( $s )) ) return $mime;
		return $this ->is_video_mime( $mime );
	}
	
	public function is_flash_video_file( $s )
	{
		if( !($mime = $this ->get_file_mime( $s )) ) return $mime;
		return $this ->is_flash_video_mime( $mime );
	}
	
	public function is_flash_file( $s )
	{
		if( !($mime = $this ->get_file_mime( $s )) ) return $mime;
		return $this ->is_flash_mime( $mime );
	}
	
	public function is_image_or_flash_file( $s )
	{
		if( !($mime = $this ->get_file_mime( $s )) ) return $mime;
		return $this ->is_image_mime( $mime ) || $this ->is_flash_mime( $mime );
	}
	
	/**
	 * Функция пытается пересобрать и сохранить изображение
	 * 
	 * При указании максимального размера - еще и уменьшаем размер файла, чтобы сильно много не весил.
	 * ! Всегда нужно использовать что-то подобное при аплоаде!
	 *
	 * @param	string $s	путь к исходному файлу картинки
	 * @param	string $d	путь к конечного изображения
	 * @param	gl_size $t	максимальный размер конечного изображения
	 * @return	boolean		ежели всё хорошо - тогда тру. а так - файльше.
	 */
	public function move_uploaded_image( $s, $d, $t = null, $asis = false )
	{
		if( !is_uploaded_file( $s ) ) return false;
		if( !$this ->is_image_file( $s ) ) return false;
		
		if( $t ) $t = new gl_size( $t );
		
		if( !$asis ) {
			if( !move_uploaded_file( $s, $d.'~' ) ) return false;
			$r = $this ->save_crop( $d.'~', $d, null, $t, 'jpg' );
			unlink( $d.'~' );
		} else {
			if( !move_uploaded_file( $s, $d ) ) return false;
			$r = !!filesize( $d );
		}
		$size = filesize( $d );
		
		$this ->log( "\"{$d}\" {$size}b" );
		
		return $r;
	}

	/**
	 * Функция пытается пересобрать и сохранить флешовый видео-файл
	 * 
	 * При указании максимального размера - еще и уменьшаем размер файла, чтобы сильно много не весил.
	 * ! Всегда нужно использовать что-то подобное при аплоаде!
	 *
	 * @param	string $s	путь к исходному flv
	 * @param	string $d	путь конечного изображения
	 * @param	gl_size $t	максимальный размер в пикселях конечного изображения
	 * @return	boolean		ежели всё хорошо - тогда тру. а так - файльше.
	 */
	public function move_uploaded_video( $s, $d, $t = null )
	{
		if( !is_uploaded_file( $s ) ) return false;
		if( !$this ->is_video_file( $s ) ) return false;
		
		if( $t ) $t = new gl_size( $t );
		
		if( !move_uploaded_file( $s, $d.'~' ) ) return false;
		$r = $this ->save_video( $d.'~', $d );
		//unlink( $d.'~' );
		$size = filesize( $d );
		
		$this ->log( "\"{$d}\" {$size}b" );
		
		return $r;
	}
	
	public function save_square( $s, $d, $i = null, $format = 'jpg' )
	{
		list( $w, $h ) = getimagesize( $s );
		if( $w == 0 ) return null;
		
		$z = min( $w, $h );
		$i = is_null( $i ) ? $z : intval( $i );
		if( $w == $h )
			return $this ->save_crop( $s, $d, null, new gl_size( $i ), $format );
		
		$r = new gl_rect( floor(($w-$z)/2), floor(($h-$z)/2), $z, $z );
		
		return $this ->save_crop( $s, $d, $r, new gl_size( $i ), $format );
		//exec("convert -background white -flatten -quality 100% -fuzz 1% -trim +repage ".$image." ".$path, $output, $var);
		//exec("convert -quality 95% -thumbnail x".($width*2)." -resize '".($width*2)."x<' -resize 50% -gravity center -crop ".$width."x".$width."+0+0 +repage ".$path." ".$path, $output, $var);
		//return true;
	}

	public function get_image_size( $s )
	{
		list( $w, $h ) = getimagesize( $s );
		if( $w == 0 ) return null;
		return new gl_size( $w, $h );
	}
	
	
	private function log( $s )
	{
		// lol?
		//if( $log = fopen(ROOT.'/.logs/script.log', 'a') ) { fputs($log,sprintf("%s [%s] %s\n",$_SERVER['REMOTE_ADDR'],date('Y-m-d-H:i:s'),$s)); fclose($log); }
	}



	/** Возвращает координату продольного прохода, на которой закнчивается
	 *
	 *  @param resource $image изображение gd2
	 *  @param int $color цвет бордюра
	 *  @param int $orient ориентация прохода (0 - горизонтальная, 1 - вертикальная)
	 *  @param int $sx начальная точка продольного прохода
	 *  @param int $sy начальная точка перпендикулярного прохода
	 *  @param int $ex конечная точка продольного прохода
	 *  @param int $ey конечная точка перпендикулярного прохода
	 *  @return int координата продольного прохода, на которой закнчивается бордюр
	 */
	protected function image_get_border($image, $color, $orient, $sx, $sy, $ex, $ey) {
		$dx = ($sx > $ex)?-1:1;
		$dy = ($sy > $ey)?-1:1;

		for ($x = $sx; ($dx > 0 && $x < $ex) || ($dx < 0 && $x > $ex); $x += $dx) {
			for ($y = $sy; ($dy > 0 && $y < $ey) || ($dy < 0 && $y > $ey); $y += $dy) {
				$c = ($orient > 0)?imagecolorat($image, $y, $x):imagecolorat($image, $x, $y);
				if ($color != $c)
					break;
			}
			if ($y != $ey)
				break;
		}

		return $x;
	}


	/** Обрезает пустые поля цвета $color изображения $image
	 *  Устанавливает ошибку IMAGE_ERROR_EMPTY и возвращает false, если получается нулевой размер изображения
	 *
	 *  @param resource
	 */
	protected function image_autocrop($image, $color) {
		# Исходные ширина и высота
		$width = imagesx($image);
		$height = imagesy($image);

		# Исходные стартовые точки
		$sx = $sy = 0;
		$ex = $width;
		$ey = $height;

		# Обрезаем
		$rsx = $this->image_get_border($image, $color, 0, 0, 0, $width, $height);
		$rsy = $this->image_get_border($image, $color, 1, 0, $rsx, $height, $width);
		$rex = $this->image_get_border($image, $color, 0, $width-1, $rsy, $rsx, $height);
		$rey = $this->image_get_border($image, $color, 1, $height-1, $rsx, $rsy, $rex);

		if ($rsx > 0 || $rsy > 0 || $rex < $width || $rey < $height) {
			$width = $rex-$rsx;
			$height = $rey-$rsy;

			if ($width == 0 || $height == 0) {
				$this->errno = IMAGE_ERROR_EMPTY_IMAGE;
				return false;
			}

			$g = imagecreatetruecolor($width, $height);
			imagecopy($g, $image, 0, 0, $rsx, $rsy, $rex-$rsx, $rey-$rsy);
			imagedestroy($image);
			return $g;
		}

		return $image;
	}


	/** Загружает изображение из файла, автоматом определяет тип.
	 *  Если указан цвет, обрезает бордер
	 *
	 *  @param string $file путь к файлу с изображением
	 *  @param int $cropcolor rgb-цвет бордера
	 *  @return resource|boolean возвращает gd2-ресурс или false, если не удалось создать ресурс.
	 */
	protected function image_load($file, $cropcolor=false) {
		# Загружаем файл
		$source = @file_get_contents($file);
		if ($source === false) {
			$this->errno = IMAGE_ERROR_READ_ERROR;
			return false;
		}

		# Создаем объект gd2
		$source = @imagecreatefromstring($source);
		if ($source === false) {
			$this->errno = IMAGE_ERROR_INVALID_FILE_TYPE;
			return false;
		}

		# Если изображение не true_color, то конвертируем
		$w = imagesx($source);
		$h = imagesy($source);
		$img = imagecreatetruecolor($w, $h);
		imagefill($img, 0, 0, imagecolorallocate($img, 255, 255, 255));
		imagecopy($img, $source, 0, 0, 0, 0, $w, $h);
		imagedestroy($source);

		# Режем бордер, если надо
		if ($cropcolor	!== false) {
			$img = $this->image_autocrop($img, $cropcolor);
			if ($img === false)
				return false;
		}

		return $img;
	}
	
	/**
	 * Ковертация видео-файла из одного формата в другой
	 *
	 * @param	string	$in	Input file
	 * @param	string	$out	Output file
	 * @param	mixed 	$f	If string get from prepared formats by key, else if array fills params manually
	 * @access	public
	 * @return	bool	true on success
	 */
	public function save_video( $in, $out, $f = "flv" )
	{
		// file verifications
		//if( !file_exists( $in )
		//	|| !($_f=fopen($out,'w')) ) return false;
		//fclose($_f);
		if( !is_readable( $in ) ) return false;
		
		// fetch format
		/*if( is_string($f) ) {
			$f = trim((string)$f);
			$f = isset( self::$__convf[$f] ) ? self::$__convf[$f] : self::$__convf['flv'];
		} elseif( is_array( $f ) ) {
			// todo: need verify
			$f = $f;
		}*/
		
		// calculate
		list( $d, $w, $h ) = $this ->info_video( $in );
		$maxw = config::instance()->video__max_width;
		$maxh = config::instance()->video__max_height;
		if( !$maxw ) $maxw = 320;
		if( !$maxh ) $maxh = 240;
		if( !is_null( $f ) )
			$out = sprintf( "%s/%s.%s", pathinfo( $out, PATHINFO_DIRNAME ), pathinfo( $out, PATHINFO_FILENAME ), $f );
		
		$tn = sprintf( "%s/%s.%s", pathinfo( $out, PATHINFO_DIRNAME ), pathinfo( $out, PATHINFO_FILENAME ), 'jpg' );
		
		$this ->log( sprintf( "out: %s, tn: %s", $out, $tn ) );
		
		$aspect = (float)$w/(float)$h;
		
		$w = $maxw; $h = floor($w/$aspect/2)*2;
		if( $h > $maxh ) { $h = $maxh; $w = floor($h*$aspect/2)*2; }
		$w = round($w); $h = round($h);
		
		//echo "$in -> $out | {$w}x$h $f\n";
		$quality = "-qblur 0.01 -qcomp 1 -qmin 2 -qmax 9"; //"-sameq"
		$mp3codec = config::instance()->video__mp3codec;
		
		$_e1 = sprintf( 'ffmpeg -i "%s" -ar 22050 -ab 56 -ac 1 -acodec %s -f %s -s %dx%d %s -y "%s"', addslashes($in), $mp3codec, $f, $w, $h, $quality, addslashes($out) );
		$this ->log( '1. '.$_e1 );
		exec( $_e1, $o, $v );
		$this ->log( '1! '.$v." ".join( ', ', $o ) );
		
		$_e2 = sprintf( 'ffmpeg -i "%s" -an -sameq -vframes 1 -ss %.2f -s %dx%d -f image2 -y "%s"', addslashes($in), $d/2, $w, $h, addslashes($tn) );
		$this ->log( '2. '.$_e2 );
		exec( $_e2, $o, $v );
		$this ->log( '2! '.$v." ".join( ', ', $o ) );
		
		//echo $_e1."\n".$_e2;
		//exec( sprintf( 'ffmpeg -i "%s" -an -sameq -vframes 1 -ss %.2f -s %dx%d -f image2 -y "%s"', addslashes($in), $d/2, $w, $h, addslashes($tn) ) );
		if( !is_readable( $out ) || !is_readable( $tn ) ) return false;
		
		return true;
	}
	
	/**
	 * Получение информации о видео потоке файла
	 *
	 * @param	string	$src	video file
	 * @access	public
	 * @return	array|null	null if not success. else full-array with duration and width_Xheight
	 */
	public function info_video( $src )
	{
		if( !is_readable( $src ) ) return null;
		exec( "ffmpeg -i \"".addslashes($src)."\" 2>&1", $rawvi );
		$rawvi = join( "\n", $rawvi );
		
		if( !preg_match( '/\bInput #[0-9]+,/', $rawvi ) ) return null;
		
		preg_match( "/ duration: ([0-9:.]+)/i", $rawvi, $found );
		$duration = 0; if( isset( $found[1] ) ) { $duration = $found[1]; $_d = explode( ':', $duration ); $duration = @$_d[0]*3600 + @$_d[1]*60 + (float)@$_d[2]; }
		preg_match( "/ video:.*[^0-9]([0-9]+x[0-9]+)[^0-9]?/i", $rawvi, $found );
		@list( $width, $height ) = explode( 'x', @$found[1] );
		
		return array( 'duration' => $duration, 'width' => $width, 'height' => $height, 0 => $duration, 1 => $width, 2 => $height );
	}
	
	/**
	 * Видео форматы, заготовки
	 *
	 */
	static public $__convf = array(
		'flv' => array( ),
		'mkv' => array( ),
		'divx' => array( ),
		);
	
	
	
	/**
	 * addons for win32
	 *
	 */
	function create_thumbnail( $s, $d, $thumb_width = 100 )
	{
		$ext = strtolower( preg_replace('|.+\.(.{3,4})$|', '\\1', $s) );
		
		$th = null;
		switch( $ext )
		{
		case 'jpg':
		case 'jpeg':	$th = imagecreatefromjpeg($s); break;
		case 'gif':	$th = imagecreatefromgif($s); break;
		case 'png':	$th = imagecreatefrompng($s); break;
		}
		if (bc::enabled()) bc::log( $s, 's' );
		if (bc::enabled()) bc::log( $th, 'th' );
		if (bc::enabled()) bc::log( 'ath' );
		
		if( $th )
		{
			$w = $width = imageSX( $th );
			$h = $height = imageSY( $th );
			
			// calc new widthXheight
			$a = $w/$h; // aspect
			if( $w == $h ) {
				$w = $h = $thumb_width;
			} elseif( $w > $h ) {
				$w = $thumb_width;
				$h = floor($w/$a/2)*2; // must be divided by 2
			} else {
				$h = $thumb_width;
				$w = floor($h*$a/2)*2; // must be divided by 2
			}
			// done
			
			$tmp_img = imagecreatetruecolor( $w, $h );
			imagecopyresized( $tmp_img, $th, 0, 0, 0, 0, $w, $h, $width, $height );
			if (bc::enabled()) bc::log( $s, $d );
			return imagejpeg( $tmp_img, $d );
		}
		return false;
	}
}

?>